#ifndef TB_REFLECTION_TYPE_CAST_HPP__
#define TB_REFLECTION_TYPE_CAST_HPP__

#include <boost/mpl/and.hpp>
#include <boost/mpl/eval_if.hpp>

#include "tbcore/config.hpp"

#include "type_traits.hpp"

TB_NAMESPACE_BEGIN

namespace detail {

// cast direction
struct up_cast_tag {};
struct down_cast_tag {};
struct equal_cast_tag {};

// cast type
struct static_cast_tag {};
struct reflection_cast_tag {};
struct polymorphic_cast_tag {};
struct failure_cast_tag {};

template <typename From, typename To, typename Tag>
struct SmartCastImpl {};

template <typename From, typename To>
struct SmartCastImpl<From, To, static_cast_tag> {
  static To* UpCast(From* obj) {
    return static_cast< To* >(obj);
  }

  static To* DownCast(From* obj) {
    return static_cast< To* >(obj);
  }
};

template <typename From, typename To>
struct SmartCastImpl<From, To, reflection_cast_tag> {
  static To* UpCast(From* obj) {
    return static_cast< To* >(obj);
  }
  
  // disable dynamic cast may improve performance
  static To* DownCast(From* obj) {
#if !defined(TB_CXX_NO_DYNAMIC_CAST)
    return dynamic_cast< To* >(obj);
#else //!defined(TB_CXX_NO_DYNAMIC_CAST)
    if (obj->IsKindOf(To::ClassTypeId())) {
      return static_cast< To* >(obj);
    } else {
      return nullptr;
    }
#endif //!defined(TB_CXX_NO_DYNAMIC_CAST)
  }
};

template <typename From, typename To>
struct SmartCastImpl<From, To, polymorphic_cast_tag> {
  static To* UpCast(From* obj) {
    return static_cast< To* >(obj);
  }

  static To* DownCast(From* obj) {
    return dynamic_cast< To* >(obj);
  }
};

template <typename From, typename To>
struct SmartCastImpl<From, To, failure_cast_tag> {
  static To* UpCast(From* obj) {
    return nullptr;
  }

  static To* DownCast(From* obj) {
    return nullptr;
  }
};

template <typename From, typename To, class TypeTag, class DirTag>
struct SmartCastDispatcher {};

template <typename From, typename To, class TypeTag>
struct SmartCastDispatcher<From, To, TypeTag, up_cast_tag> {
  static To* Cast(From* obj) {
    return SmartCastImpl<From, To, TypeFlag>::UpCast(obj);
  }
};

template <typename From, typename To, class TypeTag>
struct SmartCastDispatcher<From, To, TypeTag, down_cast_tag> {
  static To* Cast(From* obj) {
    return SmartCastImpl<From, To, TypeTag>::DownCast(obj);
  }
};

template <typename From, typename To, typename TypeFlag>
struct SmartCastDispatcher<From, To, TypeFlag, equal_cast_tag> {
  static To* Cast(From* obj) {
    BOOST_STATIC_ASSERT(is_same<From, To>::value);
    return static_cast<T*>(obj);
  }
};

template <typename To, typename From> 
struct SmartCast {
  typedef typename remove_cv<From>::type from_type;

  typedef typename remove_cv<To>::type to_type;
  
  typedef typename boost::mpl::eval_if<            
    is_same<from_type, to_type>,          
    boost::mpl::identity<equal_cast_tag>,                                   
    boost::mpl::identity<typename boost::mpl::eval_if<
      is_base_of<to_type, from_type>, 
      boost::mpl::identity<up_cast_tag>, 
      boost::mpl::identity<down_cast_tag>
    >::type >                               
  >::type dir_tag;
  
  typedef typename boost::mpl::eval_if<
    boost::mpl::and_<                                 
      is_base_of<Object, from_type>,
      is_base_of<Object, to_type>
    >,
    boost::mpl::identity<reflection_cast_tag>,                                
    boost::mpl::identity<typename boost::mpl::eval_if<                  
      boost::mpl::and_<
        is_polymorphic<from_type>,
        is_polymorphic<to_type>
      >,
      boost::mpl::identity<typename boost::mpl::eval_if<
        is_base_of<to_type, from_type>, 
        boost::mpl::identity<static_cast_tag>, 
        boost::mpl::identity<polymorphic_cast_tag>
      >::type>,
      boost::mpl::identity<polymorphic_cast_tag>
    >::type>
  >::type type_tag;
  
  static To* DynamicCast(From* obj) {
    if (!obj) {
      return nullptr;
    } else {
      return SmartCastDispatcher<From, To, type_tag, dir_tag>::Cast(const_cast<from_type*>(obj));
    }
  }
};

template <typename To, typename From>
struct SharedPtrCast {
  typedef shared_ptr<From> from_ptr_type;
  typedef shared_ptr<To> to_ptr_type;

  static to_ptr_type DynamicPointerCast(from_ptr_type const& r) {
    (void) dynamic_cast< To* >( static_cast< From* >( 0 ) );

    typedef typename from_ptr_type::element_type from_element_type;
    typedef typename to_ptr_type::element_type to_element_type;;

    to_element_type* p = SmartCast<to_element_type, from_element_type>::DynamicCast(r.get());

    return p ? to_ptr_type(r, p) : to_ptr_type();
  }
};

} //namespace detail

template <typename T, typename F> 
T DynamicCast(F r) {
  return detail::SmartCast<typename remove_pointer<T>::type, typename remove_pointer<F>::type>::DynamicCast(r);
}

template <typename T, typename F> 
T DynamicSPCast(F r) {
  return detail::SharedPtrCast<T::element_type, F::element_type>::DynamicPointerCast(r);
}

TB_NAMESPACE_END

#define TBDynamicCast(obj, To) (TB_NAMESPACE::DynamicCast<To>(obj))
#define TBDynamicSPCast(obj, To) (TB_NAMESPACE::DynamicSPCast<To>(obj))

#define TBStaticCast(obj, To) (static_cast<To>(obj))
#define TBStaticSPCast(obj, To) (TB_NAMESPACE::static_pointer_cast<To::element_type>(obj))

#define TBConstCast(obj, To) (const_cast<To>(obj))
#define TBConstSPCast(obj, To) (TB_NAMESPACE::const_pointer_cast<To::element_type>(obj))

#endif //TB_REFLECTION_TYPE_CAST_HPP__
